/**
 * Ascensor.cpp
 *
 */

#include "Ascensor.h"

/**
 * Constructor del ascensor.
 * Recibe los parametros de ejecucion del ascensor, los semaforos para
 * sincronizarse con el controlador y un struct con punteros a los pipes
 * de todos los pisos para subir personas al ascensor.
 */
Ascensor::Ascensor( int velocidad, int capacidad, int cantidadPisos, Semaforos semaforos, PipePersonas pipePersonas )
	: velocidad ( velocidad ),
	  capacidad ( capacidad ),
	  cantidadPisos ( cantidadPisos ),
	  pisoActual ( 0 ),
	  modo ( MODO_ESPERA ),
	  cumplirLlamadas ( true ),
	  personas ( list<Persona>() ),
	  llamadas ( list<Llamada>() ),
	  memComp ( MemoriaCompartida<Llamada>() ),
	  semaforos( semaforos ),
	  pipePersonas ( pipePersonas ),
	  cantidadSubieron ( vector<int>() ),
	  cantidadBajaron ( vector<int>() ){

}

/**
 * Metodo que inicializa los mecanismos de concurrencia que usa el
 * ascensor.
 * Registra los handlers de seniales e inicializa la memoria compartida.
 * Ademas inicializa contadores para registrar la cantidad de personas
 * que suben y bajan en cada piso.
 */
int Ascensor::inicializar() {
	int retorno = -1;

	SignalHandler::getInstance()->registrarHandler( SIGUSR1, this );
	SignalHandler::getInstance()->registrarHandler( SIGUSR2, this );
	SignalHandler::getInstance()->registrarHandler( SIGALRM, this );
	SignalHandler::getInstance()->registrarHandler( SIGINT, this );
	SignalHandler::getInstance()->registrarHandler( SIGTERM, this );
	SignalHandler::getInstance()->registrarHandler( SIGABRT, this );

	if ( this->memComp.crear( (char*) ARCHIVO_MEM_COMP, ID_MEM_COMP ) != SHM_OK ) {
		Log::getInstance()->loguear("Ascensor: Error al crear la memoria compartida.");
	} else if ( this->velocidad == 0 ) {
		Log::getInstance()->loguear("Ascensor: La velocidad no puede ser 0.");
	} else {
		for ( int i = 0; i <= this->cantidadPisos; i++ ){
			this->cantidadSubieron.push_back(0);
			this->cantidadBajaron.push_back(0);
		}
		Log::getInstance()->loguear("Ascensor: creado.");
		retorno = 0;
	}

	return retorno;
}

/**
 * Destructor del ascensor.
 * Elimina los handlers de seniales, libera la memoria compartida
 * con el controlador, cierra los pipes de personas y elimina los
 * semaforos.
 */
Ascensor::~Ascensor() {
	SignalHandler::getInstance()->removerHandler ( SIGUSR1 );
	SignalHandler::getInstance()->removerHandler ( SIGUSR2 );
	SignalHandler::getInstance()->removerHandler ( SIGALRM );
	SignalHandler::getInstance()->removerHandler ( SIGINT );
	SignalHandler::getInstance()->removerHandler ( SIGTERM );
	SignalHandler::getInstance()->removerHandler ( SIGABRT );
	SignalHandler::destruir();

	this->memComp.liberar();

	for ( int i = 0; i <= this->cantidadPisos; i++ ){
		this->pipePersonas.pipePersonasSuben[ i ]->cerrar();
		this->pipePersonas.pipePersonasBajan[ i ]->cerrar();
	}

	this->semaforos.escritura->eliminar();
	this->semaforos.lectura->eliminar();

	Log::getInstance()->loguear("Ascensor: destruido.");
	this->mostrarReporte();
}

/**
 * Handler de seniales:
 * SIGUSR1: Senial que envia el controlador cuando coloca una llamada
 * 		en la memoria compartida.
 * SIGUSR2: Senial que envia el terminador para que el ascensor finalice
 * 		la ejecucion en forma ordenada, terminando de cumplir las llamadas
 * 		que tiene.
 * SIGALRM: Senial que el ascensor se envia a si mismo para saber cuando
 * 		arriba a un nuevo piso.
 * SIGINT: Senial que el usuario envia desde fuera de la aplicacion.
 * 		Se ignora y se deja que el proceso terminador las agarre y procese.
 * SIGTERM: Senial que el usuario envia desde fuera de la aplicacion.
 * 		Se ignora y se deja que el proceso terminador las agarre y procese.
 * SIGABRT: Senial que envia el terminador en caso de que haya errores.
 * 		Se finaliza la ejecucion inmediatamente liberando los recursos.
 */
int Ascensor::handleSignal( int signum ){
	if ( signum == SIGUSR1 ){
		Log::getInstance()->loguear("Ascensor: me llego SIGUSR1. Voy a atender llamado del controlador.");
		this->leerLlamada();

	} else if ( signum == SIGUSR2 ){
		Log::getInstance()->loguear("Ascensor: me llego SIGUSR2. Voy a finalizar.");
		this->cumplirLlamadas = false;

	} else if ( signum == SIGALRM ){
		Log::getInstance()->loguear("Ascensor: me llego SIGALRM. Llegue a un nuevo piso." );
		this->arriboPiso();

	} else if ( signum == SIGINT ){
		Log::getInstance()->loguear("Ascensor: me llego SIGINT. No hago nada." );
		// El terminador (padre) se encarga de agarrar la senial

	} else if ( signum == SIGTERM ){
		Log::getInstance()->loguear("Ascensor: me llego SIGTERM. No hago nada." );
		// El terminador (padre) se encarga de agarrar la senial

	} else if ( signum == SIGABRT ){
		Log::getInstance()->loguear("Ascensor: me llego SIGABRT. Finalizo ante errores." );
		this->~Ascensor();
		exit( -1 );
	}

	return 0;
}

/**
 * Metodo que lee las llamadas desde la memoria compartida cuando
 * el controlador le envia una senial.
 */
void Ascensor::leerLlamada(){
	Llamada llamada = this->memComp.leer();
	Log::getInstance()->loguear("Ascensor: Agrego una llamada del piso " + intToString( llamada.getPiso() ) );

	this->llamadas.push_back( llamada );
	this->analizarLlamadas();

	this->semaforos.escritura->v();
}

/**
 * Metodo que se ejecuta cuando le llega una nueva llamada al ascensor
 * desde el controlador. Si el ascensor estaba parado, analiza la llamada
 * y decide que hacer.
 * Si el ascensor no estaba en modo 'Espera', entonces no hace nada, ya que
 * la llamada se analizara cuando el ascensor llegue a un nuevo piso.
 */
void Ascensor::analizarLlamadas(){
	if ( this->modo == MODO_ESPERA ){
		if ( hayLlamadaPisoActual() ){
			this->subirPersonas();
		}
		this->decidirSiguienteModo();
	}
}

/**
 * Metodo que se ejecuta cuando el ascensor llega a un nuevo piso.
 * Actualiza el piso donde se encuentra el ascensor y, si hay llamadas
 * para este piso, inicia la carga y descarga de personas.
 */
void Ascensor::arriboPiso(){
	if ( this->modo == MODO_SUBIR ){
		this->pisoActual++;
	} else if ( this->modo == MODO_BAJAR ){
		this->pisoActual--;
	}

	Log::getInstance()->loguear("Ascensor: estoy en el piso: " + intToString( this->pisoActual ));

	if ( this->hayLlamadaPisoActual() ){
		this->bajarPersonas();
		this->subirPersonas();
	}

	this->decidirSiguienteModo();
}

/**
 * Devuelve true si existe alguna llamada en el piso donde se encuentra
 * el ascensor.
 */
bool Ascensor::hayLlamadaPisoActual(){
	bool hayLlamadas = false;

	list<Llamada>::iterator it = this->llamadas.begin();
	for ( ; it != this->llamadas.end(); ++it ){
		if ( it->getPiso() == this->pisoActual ){
			hayLlamadas = true;
		}
	}
	return hayLlamadas;
}

/**
 * Devuelve true si existe alguna llamada en pisos debajo de donde esta
 * el ascensor.
 */
bool Ascensor::hayLlamadaPisoAbajo(){
	bool hayLlamadas = false;

	list<Llamada>::iterator it = this->llamadas.begin();
	for ( ; it != this->llamadas.end(); ++it ){
		if ( it->getPiso() < this->pisoActual ){
			hayLlamadas = true;
		}
	}
	return hayLlamadas;
}

/**
 * Devuelve true si existe alguna llamada en pisos arriba. de donde esta
 * el ascensor
 */
bool Ascensor::hayLlamadaPisoArriba(){
	bool hayLlamadas = false;

	list<Llamada>::iterator it = this->llamadas.begin();
	for ( ; it != this->llamadas.end(); ++it ){
		if ( it->getPiso() > this->pisoActual ){
			hayLlamadas = true;
		}
	}
	return hayLlamadas;
}

/**
 * Metodo que decide el siguiente modo en que se pondra el ascensor.
 * Prioriza el modo actual, es decir, si el ascensor esta en modo
 * 'Subir' tendera a permanecer en ese modo si es que hay llamadas en
 * pisos arriba.
 *
 * Ademas es el metodo encargado de poner una alarma para notificar al
 * ascensor cuando llego a un nuevo piso.
 * La alarma solo se pone si el ascensor no quedo en modo 'Espera'.
 *
 */
void Ascensor::decidirSiguienteModo(){
	if ( this->modo == MODO_SUBIR ){

		if ( this->hayLlamadaPisoArriba() ){
			// Continua en modo subir
			alarm ( this->getTiempoEntrePisos() );
		} else if ( this->hayLlamadaPisoAbajo() ) {
			this->modo = MODO_BAJAR;
			alarm ( this->getTiempoEntrePisos() );
		} else {
			this->modo = MODO_ESPERA;
		}

	} else if ( this->modo == MODO_BAJAR ){

		if ( this->hayLlamadaPisoAbajo() ){
			// Continua en modo bajar
			alarm ( this->getTiempoEntrePisos() );
		} else if ( this->hayLlamadaPisoArriba() ) {
			this->modo = MODO_SUBIR;
			alarm ( this->getTiempoEntrePisos() );
		} else {
			this->modo = MODO_ESPERA;
		}

	} else { // MODO ESPERA

		// No hay nadie adentro, se setea el modo de la primera llamada
		if ( ! this->llamadas.empty() ){
			if ( this->llamadas.front().getPiso() > this->pisoActual ){
				this->modo = MODO_SUBIR;
			} else {
				this->modo = MODO_BAJAR;
			}
			alarm ( this->getTiempoEntrePisos() );
		} else {
			Log::getInstance()->loguear("Ascensor en piso " + intToString( this->pisoActual ) + ": No tengo mas llamadas.");
		}

	}
	Log::getInstance()->loguear("Ascensor: estoy en modo " + this->getStringModo() );
}

/**
 * Metodo que baja las personas del ascensor.
 * Las personas se eliminan de la lista de personas que tiene el ascensor,
 * y ademas se elimina la llamada interna que se corresponde con la persona.
 *
 * Si se bajaron todas las personas y el ascensor quedo vacio, se pone en
 * modo 'Espera'.
 */
void Ascensor::bajarPersonas(){
	list<Persona>::iterator itPersonas = this->personas.begin();
	for ( ; itPersonas != this->personas.end();  ){
		if ( itPersonas->getPisoDestino() == this->pisoActual ){
			itPersonas = this->personas.erase(itPersonas);
			Log::getInstance()->imprimir("Ascensor en piso " + intToString( this->pisoActual ) + ": se baja una persona.");
			Log::getInstance()->loguear("Ascensor en piso " + intToString( this->pisoActual ) + ": se baja una persona. Capacidad actual: " + intToString( this->personas.size() ) );
			this->cantidadBajaron[ this->pisoActual ] += 1;
		} else {
			++itPersonas;
		}
	}
	list<Llamada>::iterator itLlamadas = this->llamadas.begin();
	for ( ; itLlamadas != this->llamadas.end(); ){
		if ( itLlamadas->getPiso() == this->pisoActual && itLlamadas->getTipoLlamada() == LLAMADA_INTERNA ){
			itLlamadas = this->llamadas.erase(itLlamadas);
			Log::getInstance()->loguear("Ascensor en piso " + intToString( this->pisoActual ) + ": se elimina una llamada interna.");
		} else {
			++itLlamadas;
		}
	}

	if ( this->personas.empty() ){
		Log::getInstance()->loguear("Ascensor en piso " + intToString( this->pisoActual ) + ". Estoy vacio. Me pongo en modo Espera");
		this->modo = MODO_ESPERA;
	}
}

/**
 * Metodo que lee de un pipe del piso las personas que haya que subir al
 * ascensor segun el modo en que se encuentre el ascensor.
 * Por ejemplo, si hay personas para bajar, pero el ascensor esta en modo
 * 'Subir', estas personas no suben al ascensor.
 * Si el ascensor esta vacio, entonces esta en modo 'Espera', la primera
 * persona que suba definira el proximo modo del ascensor.
 *
 * Cuando una persona sube al ascensor, la llamada que tenia no se elimina,
 * sino que se transforma en una llamada interna, actualizando el piso al
 * que la persona quiere ir.
 */
void Ascensor::subirPersonas(){
	Persona persona;
	list<Llamada>::iterator it = this->llamadas.begin();
	for ( ; ( (it != this->llamadas.end() ) &&  ( this->personas.size() < this->capacidad ) ) ; ++it ){
		if ( it->getPiso() == this->pisoActual ){
			if ( this->modo == MODO_SUBIR && it->getTipoLlamada() == LLAMADA_SUBIR ) {

				this->pipePersonas.pipePersonasSuben[ this->pisoActual ]->leer( &persona, sizeof( Persona ) );
				this->personas.push_back( persona );
				it->setPiso( persona.getPisoDestino() );
				it->setTipoLlamada( LLAMADA_INTERNA );
				Log::getInstance()->imprimir("Ascensor en piso " + intToString( this->pisoActual ) + ": ingresa una persona que va al piso " + intToString( persona.getPisoDestino() ) + ".");
				Log::getInstance()->loguear("Ascensor en piso " + intToString( this->pisoActual ) + ": ingresa una persona que va al piso " + intToString( persona.getPisoDestino() ) + ". Capacidad actual: " + intToString( this->personas.size() ) + ".");
				this->cantidadSubieron[ this->pisoActual ] += 1;

			} else if ( this->modo == MODO_BAJAR && it->getTipoLlamada() == LLAMADA_BAJAR ) {

				this->pipePersonas.pipePersonasBajan[ this->pisoActual ]->leer( &persona, sizeof( Persona ) );
				this->personas.push_back( persona );
				it->setPiso( persona.getPisoDestino() );
				it->setTipoLlamada( LLAMADA_INTERNA );
				Log::getInstance()->imprimir("Ascensor en piso " + intToString( this->pisoActual ) + ": ingresa una persona que va al piso " + intToString( persona.getPisoDestino() ) + ".");
				Log::getInstance()->loguear("Ascensor en piso " + intToString( this->pisoActual ) + ": ingresa una persona que va al piso " + intToString( persona.getPisoDestino() ) + ". Capacidad actual: " + intToString( this->personas.size() ) + ".");
				this->cantidadSubieron[ this->pisoActual ] += 1;

			} else if ( this->modo == MODO_ESPERA ){
				// No hay nadie dentro del ascensor, subo a uno y ese setea el modo
				if ( it->getTipoLlamada() == LLAMADA_BAJAR ){
					this->pipePersonas.pipePersonasBajan[ this->pisoActual ]->leer( &persona, sizeof( Persona ) );
					this->modo = MODO_BAJAR;
				} else {
					this->pipePersonas.pipePersonasSuben[ this->pisoActual ]->leer( &persona, sizeof( Persona ) );
					this->modo = MODO_SUBIR;
				}
				this->personas.push_back( persona );
				it->setPiso( persona.getPisoDestino() );
				it->setTipoLlamada( LLAMADA_INTERNA );
				Log::getInstance()->imprimir("Ascensor en piso " + intToString( this->pisoActual ) + ": ingresa una persona que va al piso " + intToString( persona.getPisoDestino() ) + ".");
				Log::getInstance()->loguear("Ascensor en piso " + intToString( this->pisoActual ) + ": ingresa una persona que va al piso " + intToString( persona.getPisoDestino() ) + ". Capacidad actual: " + intToString( this->personas.size() ) + ".");
				this->cantidadSubieron[ this->pisoActual ] += 1;
			}
		}
	}

}

/**
 * Devuelve el tiempo en segundos que tarda el ascensor en ir de un
 * piso a otro.
 */
int Ascensor::getTiempoEntrePisos(){
	return DISTANCIA_ENTRE_PISOS / this->velocidad;
}

/**
 * Metodo que se ejecuta mientras el ascensor deba cumplir llamadas
 * (no se haya terminado el tiempo de simulacion) o todavia le queden
 * llamadas por cumplir.
 */
void Ascensor::funcionar(){
	while ( ( this->cumplirLlamadas ) || ( ! this->llamadas.empty() ) ){
		pause();
	}
}

/**
 * Metodo que imprime la cantidad total de personas que subieron al ascensor
 * y que bajaron de el.
 */
void Ascensor::mostrarReporte(){
	int totalSubieron = 0;
	for (int i = 0; i <= this->cantidadPisos; i++){
		Log::getInstance()->loguear("Ascensor: Cantidad de personas que subieron en piso " + intToString( i ) + ": " + intToString( this->cantidadSubieron[ i ] ) );
		totalSubieron += this->cantidadSubieron[ i ];
	}
	Log::getInstance()->imprimir("Ascensor: Cantidad de personas total que subieron: " + intToString( totalSubieron ) + "." );

	int totalBajaron = 0;
	for (int i = 0; i <= this->cantidadPisos; i++){
		Log::getInstance()->loguear("Ascensor: Cantidad de personas que bajaron en piso " + intToString( i ) + ": " +  intToString( this->cantidadBajaron[ i ] ) );
		totalBajaron += this->cantidadBajaron[ i ];
	}
	Log::getInstance()->imprimir("Ascensor: Cantidad de personas total que bajaron: " + intToString( totalBajaron ) + "." );
}

/**
 * Devuelve un string segun el modo en que se encuentre el ascensor.
 */
string Ascensor::getStringModo(){
	if ( this->modo == MODO_SUBIR ){
		return "Subir";
	} else if ( this->modo == MODO_BAJAR ){
		return "Bajar";
	} else {
		return "Espera";
	}
}
